﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Lucene.Net.Analysis;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;

namespace JH
{
    /// <summary>
    ///  字符串模糊匹配器,泛型
    /// </summary>
    /// <typeparam name="T">分析器类型</typeparam>
    public class FuzzyMatcherT<T> :FuzzyMatcherBase where T : Analyzer
    {
        /// <summary>
        /// 定义Lucene.NET版本常量，确保整个应用程序使用一致的版本
        /// </summary>
        private const LuceneVersion AppLuceneVersion = LuceneVersion.LUCENE_48;

        /// <summary>
        /// 用于存储索引的目录
        /// </summary>
        private readonly Directory _directory;

        /// <summary>
        /// 分析器，用于处理英文文本
        /// </summary>
        private readonly T _analyzer;

        /// <summary>
        /// 索引写入器，用于添加和更新索引
        /// </summary>
        private readonly IndexWriter _writer;

        /// <summary>
        /// 索引搜索器，用于执行搜索操作
        /// </summary>
        private IndexSearcher _searcher;

        /// <summary>
        ///  单个线程最少处理的数量
        /// </summary>
        private const int MinCountEachThreadDeal = 100;

        public FuzzyMatcherT()
        {
            // 初始化内存中的索引目录
            // RAMDirectory: 使用内存存储索引，适合小型应用或测试。对于大型应用，可能需要考虑使用 FSDirectory 来存储在文件系统中。
            _directory = new RAMDirectory();

            // 创建标准分析器实例
            _analyzer = typeof(T).GetConstructor(new[] {typeof(LuceneVersion)})
                ?.Invoke(new object[] {AppLuceneVersion}) as T;

            // 创建索引写入器配置
            var config = new IndexWriterConfig(AppLuceneVersion, _analyzer);

            // 初始化索引写入器
            _writer = new IndexWriter(_directory, config);
        }

        /// <summary>
        ///  添加多个文本到索引  ，并提交变更
        /// </summary>
        public override void AddStringsAndCommit(IEnumerable<string> textArray)
        {
            // 逐个添加文本到索引
            foreach (var text in textArray) AddString(text);

            // 提交变更，确保索引被更新
            _writer.Commit();
        }

        /// <summary>
        ///  添加多个文本到索引
        /// </summary>
        public override async Task AddStringsAndCommitAsync(IEnumerable<string> textEnumerable,
            CancellationToken token = default)
        {
            var textArray = textEnumerable == null ? Array.Empty<string>() : textEnumerable.ToArray();

            //分批处理
            int totalCount = textArray.Length;
            int batchSize = MinCountEachThreadDeal;
            int batchesCount = (totalCount + batchSize - 1) / batchSize; //一种向上取整的算法
            string[][] batches = new string[batchesCount][];
            for (int i = 0; i < batchesCount; i++)
            {
                int start = i * batchSize;
                int length = Math.Min(batchSize, totalCount - start);
                batches[i] = new string[length];
                Array.Copy(textArray, start, batches[i], 0, length);
            }

            // 每一批开启一个线程处理
            var taskList = batches.Select(batch => Task.Run(() =>
            {
                // 逐个添加文本到索引
                foreach (var text in batch) AddString(text);
            }, token)).ToList();

            // 等待所有任务完成
            await Task.WhenAll(taskList);

            // 提交变更，确保索引被更新
            _writer.Commit();
        }

        /// <summary>
        ///  添加文本到索引，并提交变更
        /// </summary>
        public override void AddStringAndCommit(string text)
        {
            // 添加文本到索引
            AddString(text);

            // 提交变更，确保索引被更新
            _writer.Commit();
        }

        /// <summary>
        ///  添加文本到索引
        /// </summary>
        private void AddString(string text)
        {
            // 创建一个新的文档对象
            var doc = new Document
            {
                // 添加一个文本字段，存储和索引输入的文本
                // 用于存储和索引文本内容。Field.Store.YES 表示原始文本也会被存储，便于检索。
                new TextField("content", text, Field.Store.YES)
            };

            // 将文档添加到索引中
            _writer.AddDocument(doc);
        }

        public override List<string> Search(string query, int maxResults = 10)
        {
            var results = new List<string>();

            // 更新搜索器以反映新添加的文档
            UpdateSearcher();

            // 创建查询解析器，指定使用的字段和分析器
            //负责将用户输入的查询字符串转换为 Lucene 可以理解的查询对象。
            var parser = new QueryParser(AppLuceneVersion, "content", _analyzer);

            // 构造查询字符串，包含精确匹配、模糊匹配和前缀匹配
            var exactQuery = QueryParserBase.Escape(query);
            var fuzzyQuery = QueryParserBase.Escape(query) + "~2";
            var prefixQuery = QueryParserBase.Escape(query) + "*";
            var queryString = $"{exactQuery} OR {fuzzyQuery} OR {prefixQuery}";

            // 解析查询字符串
            var parsedQuery = parser.Parse(queryString);

            // 执行搜索，获取结果
            var hits = _searcher.Search(parsedQuery, maxResults).ScoreDocs;

            // 遍历搜索结果
            foreach (var hit in hits)
            {
                // 获取文档内容
                var doc = _searcher.Doc(hit.Doc);
                var content = doc.Get("content");
                results.Add(content);
            }

            return results;
        }

        private void UpdateSearcher()
        {
            // 释放旧的搜索器（如果存在）
            _searcher?.IndexReader?.Dispose();

            // 创建新的搜索器，使用最新的索引读取器
            _searcher = new IndexSearcher(_writer.GetReader(applyAllDeletes: true));
        }

        public override void Dispose()
        {
            // 释放资源，确保正确关闭索引写入器和目录
            _writer.Dispose();
            _directory.Dispose();
        }
    }
}